////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-2018 Qualcomm Technologies, Inc.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file  camxcmdbuffermanager.cpp
/// @brief CmdBufferManager class implementation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "camxcmdbuffermanager.h"
#include "camxcmdbuffer.h"
#include "camxincs.h"
#include "camxlist.h"
#include "camxmem.h"
#include "camxpacket.h"
#include "camxpacketdefs.h"
#include "camxpacketresource.h"

CAMX_NAMESPACE_BEGIN

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Constant Definitions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static const UINT MaxNumAllocations = 100;  ///< Max number of allocations


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CmdBufferManager::Create
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult CmdBufferManager::Create(
    const ResourceParams* pParams,
    CmdBufferManager**    ppCmdBufferManager)
{
    CamxResult        result            = CamxResultSuccess;
    CmdBufferManager* pCmdBufferManager = NULL;

    CAMX_ASSERT((NULL != pParams) && (NULL != ppCmdBufferManager));

    if ((NULL != pParams) && (NULL != ppCmdBufferManager))
    {
        pCmdBufferManager = CAMX_NEW CmdBufferManager();
        if (NULL != pCmdBufferManager)
        {
            result = pCmdBufferManager->Initialize(pParams);

            if (CamxResultSuccess == result)
            {
                *ppCmdBufferManager = pCmdBufferManager;
            }
        }
        else
        {
            result = CamxResultENoMemory;
        }
    }
    else
    {
        result = CamxResultEInvalidArg;
    }

    if (CamxResultSuccess != result)
    {
        if (NULL != pCmdBufferManager)
        {
            CAMX_DELETE pCmdBufferManager;
            pCmdBufferManager = NULL;
        }
    }
    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CmdBufferManager::CmdBufferManager
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CmdBufferManager::CmdBufferManager()
{
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::InitializePool
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult CmdBufferManager::InitializePool()
{
    CamxResult result = CamxResultSuccess;

    CAMX_ASSERT((0 != m_params.resourceSize) && (0 == (m_params.poolSize % m_params.resourceSize)));

    if ((0 == m_params.resourceSize) || (0 != (m_params.poolSize % m_params.resourceSize)))
    {
        result = CamxResultEInvalidArg;
    }
    else
    {
        // Only allocate one big memory and break it up into various resources.
        // We don't have to do it this way because KMD ensures the allocations of a particular type are
        // mapped in one area; however, it seems it's easier to manage and debug if all the allocations of a
        // manager use the same handle.
        SIZE_T  allocationSize  = 0;
        UINT32  flags           = m_params.memFlags;
        m_numResources          = m_params.poolSize / m_params.resourceSize;

        CAMX_ASSERT(m_params.usageFlags.cmdBuffer | m_params.usageFlags.packet);

        if (1 == m_params.usageFlags.packet)
        {
            flags |= CSLMemFlagPacketBuffer | CSLMemFlagKMDAccess | CSLMemFlagUMDAccess;
        }
        else
        {
            flags |= CSLMemFlagCmdBuffer;

            if (1 == m_params.cmdParams.enableAddrPatching)
            {
                flags |= CSLMemFlagKMDAccess;
            }

            if ((CmdType::CDMDirect     == m_params.cmdParams.type) ||
                (CmdType::CDMIndirect   == m_params.cmdParams.type) ||
                (CmdType::CDMDMI        == m_params.cmdParams.type) ||
                (CmdType::CDMDMI32      == m_params.cmdParams.type) ||
                (CmdType::CDMDMI64      == m_params.cmdParams.type))
            {
                flags |= CSLMemFlagHw;
            }

            if (CmdType::FW == m_params.cmdParams.type)
            {
                flags |= CSLMemFlagHw | CSLMemFlagSharedAccess;
            }
        }

        // We need to allocate one extra DWORD for overrun check purposes
        UINT32 paddedSize = GetPaddedSize(m_params.resourceSize, m_params.alignment);
        allocationSize    = m_numResources * paddedSize;

        CAMX_ASSERT_MESSAGE(0 != allocationSize, "Num of resource: %d, padded size: %d",
                            m_numResources, paddedSize);

        CSLBufferInfo*  pBufferInfo = static_cast<CSLBufferInfo*>(CAMX_CALLOC(sizeof(CSLBufferInfo)));
        if (NULL != pBufferInfo)
        {
            result = CSLAlloc("CmdBufferManager",
                              pBufferInfo,
                              allocationSize,
                              m_params.alignment,
                              flags,
                              m_params.pDeviceIndices,
                              m_params.numDevices);
        }

        if ((CamxResultSuccess != result) || (NULL == pBufferInfo) || (CSLInvalidHandle == pBufferInfo->hHandle))
        {
            CAMX_LOG_ERROR(CamxLogGroupUtils, "Out of memory");

            result = CamxResultENoMemory;
        }
        else
        {
            CAMX_LOG_VERBOSE(CamxLogGroupUtils, "CSLAlloc returned pBufferInfo->fd=%d", pBufferInfo->fd);

            UINT32  resourceOffset = 0;
            UINT32  resourceSize   = paddedSize;

            // Create the actual command buffer resource objects and add to the corresponding bucket.
            for (UINT j = 0; j < m_numResources; j++)
            {
                // We pass the client-requested size as the size of the resource not the aligned size.
                PacketResource* pResource = NULL;

                result = CreateResource(pBufferInfo, resourceOffset, &m_params, &pResource);

                if (CamxResultSuccess == result)
                {
                    CAMX_ASSERT(NULL != pResource);

                    LDLLNode* pNode = static_cast<LDLLNode*>(CAMX_CALLOC(sizeof(LDLLNode)));

                    if (NULL != pNode)
                    {
                        pNode->pData = pResource;

                        m_freePool.InsertToTail(pNode);
                        resourceOffset += resourceSize;
                    }
                    else
                    {
                        CAMX_LOG_ERROR(CamxLogGroupUtils, "Out of memory");
                        result = CamxResultENoMemory;
                    }
                }
                else
                {
                    CAMX_LOG_ERROR(CamxLogGroupUtils, "Failed to create resource.");
                    break;
                }
            }

            if (CamxResultSuccess == result)
            {
                LDLLNode* pNode = static_cast<LDLLNode*>(CAMX_CALLOC(sizeof(LDLLNode)));

                if (pNode != NULL)
                {
                    pNode->pData = pBufferInfo;
                    m_CSLAllocations.InsertToTail(pNode);
                }
                else
                {
                    result = CamxResultENoMemory;
                }
            }

            if (CamxResultSuccess != result)
            {
                CAMX_LOG_ERROR(CamxLogGroupUtils, "Pool initialization failed: releasing resources...");
                UninitializePools();
                CSLReleaseBuffer(pBufferInfo->hHandle);
                CAMX_DELETE pBufferInfo;
                pBufferInfo = NULL;
            }
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::UninitializePools
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID CmdBufferManager::UninitializePools()
{
    m_pLock->Lock();
    LDLLNode* pNode = m_freePool.RemoveFromHead();

    while (NULL != pNode)
    {
        PacketResource* pResource = static_cast<PacketResource*>(pNode->pData);
        pResource->Destroy();

        CAMX_FREE(pNode);

        pNode = m_freePool.RemoveFromHead();
    }

    pNode = m_busyPool.RemoveFromHead();

    while (NULL != pNode)
    {
        PacketResource* pResource = static_cast<PacketResource*>(pNode->pData);
        pResource->Destroy();

        CAMX_FREE(pNode);

        pNode = m_busyPool.RemoveFromHead();
    }
    m_pLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::CreateResource
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult CmdBufferManager::CreateResource(
    CSLBufferInfo*      pBufferInfo,
    UINT32              offset,
    ResourceParams*     pParams,
    PacketResource**    ppResource)
{
    CamxResult result = CamxResultSuccess;

    if ((NULL == ppResource) || (0 == pParams->resourceSize))
    {
        CAMX_LOG_ERROR(CamxLogGroupUtils, "Invalid args");
        result = CamxResultEInvalidArg;
    }
    else
    {
        if (TRUE == pParams->usageFlags.cmdBuffer)
        {
            result = CmdBuffer::Create(&(pParams->cmdParams),
                                       pBufferInfo,
                                       offset,
                                       pParams->resourceSize,
                                       reinterpret_cast<CmdBuffer**>(ppResource));
        }
        else if (TRUE == pParams->usageFlags.packet)
        {
            result = Packet::Create(&(pParams->packetParams),
                                    pBufferInfo,
                                    offset,
                                    pParams->resourceSize,
                                    reinterpret_cast<Packet**>(ppResource));
        }

        if (CamxResultSuccess == result)
        {
            (*ppResource)->SetUsageFlags(pParams->usageFlags);
        }
        else
        {
            CAMX_LOG_ERROR(CamxLogGroupUtils, "Failed to process create request  cmd flag: %d, packet flag: %d, result: %d",
                           pParams->usageFlags.cmdBuffer, pParams->usageFlags.packet, result);
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::Initialize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult CmdBufferManager::Initialize(
    const ResourceParams*   pParams)
{
    CamxResult result = CamxResultSuccess;

    if (TRUE == m_initialized)
    {
        CAMX_LOG_ERROR(CamxLogGroupUtils, "Invalid state");
        result = CamxResultEInvalidState;
    }
    else if (NULL == pParams)
    {
        CAMX_LOG_ERROR(CamxLogGroupUtils, "Invalid args");
        result = CamxResultEInvalidArg;
    }
    else
    {
        m_params         = *pParams;

        result = InitializePool();
    }

    m_pLock = Mutex::Create("CmdBufferManager");

    if (NULL == m_pLock)
    {
        result = CamxResultENoMemory;
    }

    if (CamxResultSuccess == result)
    {
        m_initialized = TRUE;
    }
    else
    {
        FreeResources();
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::Uninitialize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID CmdBufferManager::Uninitialize()
{
    if (TRUE == m_initialized)
    {
        FreeResources();
        m_initialized = FALSE;

        if (NULL != m_pLock)
        {
            m_pLock->Destroy();
            m_pLock = NULL;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::~CmdBufferManager
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CmdBufferManager::~CmdBufferManager()
{
    Uninitialize();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::GetPaddedSize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UINT32 CmdBufferManager::GetPaddedSize(
    UINT32 sizeInBytes,
    UINT32 alignmentInBytes)
{
    return static_cast<UINT32>(Utils::ByteAlign(sizeInBytes + sizeof(CamxCanary), alignmentInBytes));
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::FreeMemHandles
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID CmdBufferManager::FreeMemHandles()
{
    LDLLNode* pNode = m_CSLAllocations.RemoveFromHead();

    while (NULL != pNode)
    {
        CSLBufferInfo* pBufferInfo = static_cast<CSLBufferInfo*>(pNode->pData);
        CAMX_ASSERT(NULL != pBufferInfo);

        CSLReleaseBuffer(pBufferInfo->hHandle);
        CAMX_FREE(pBufferInfo);
        pBufferInfo = NULL;

        CAMX_FREE(pNode);
        pNode = m_CSLAllocations.RemoveFromHead();
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::FreeResources
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID CmdBufferManager::FreeResources()
{
    // free what's still in the pool
    UninitializePools();
    FreeMemHandles();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::Recycle
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID CmdBufferManager::Recycle(
    PacketResource* pResource)
{
    CAMX_ASSERT(TRUE == m_initialized);
    CAMX_ASSERT(NULL != pResource);

    m_pLock->Lock();

    if (NULL != pResource)
    {
        LDLLNode* pNode = static_cast<LDLLNode*>(CAMX_CALLOC(sizeof(LDLLNode)));
        if (NULL != pNode)
        {
            pNode->pData = pResource;

            m_freePool.InsertToTail(pNode);
        }
        else
        {
            CAMX_LOG_ERROR(CamxLogGroupUtils, "Out of memory");
        }
    }

    m_pLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::RecycleAll
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID CmdBufferManager::RecycleAll(
    UINT64 requestId)
{
    CAMX_ASSERT(TRUE == m_initialized);

    m_pLock->Lock();

    LDLLNode* pNode = m_busyPool.Head();

    while (NULL != pNode)
    {
        PacketResource* pResource = static_cast<PacketResource*>(pNode->pData);
        CAMX_ASSERT(NULL != pResource);

        LDLLNode* pNext = LightweightDoublyLinkedList::NextNode(pNode);

        // If the resource's timestamp has retired reuse it.
        /// @note The assumption is we don't worry about wrap-around of requestIds (64bit)
        if (requestId == pResource->GetRequestId())
        {
            m_busyPool.RemoveNode(pNode);
            m_freePool.InsertToTail(pNode);
        }

        pNode = pNext;
    }

    m_pLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::GetBuffer
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult CmdBufferManager::GetBuffer(
    PacketResource** ppBuffer)
{
    CAMX_ASSERT(NULL != ppBuffer);
    CAMX_ASSERT(TRUE == m_initialized);

    CamxResult result = CamxResultSuccess;

    m_pLock->Lock();

    LDLLNode* pNode = m_freePool.RemoveFromHead();

    if (NULL != pNode)
    {
        *ppBuffer = static_cast<PacketResource*>(pNode->pData);

        if (NULL == *ppBuffer)
        {
            result = CamxResultEInvalidState;
        }
        else
        {
            (*ppBuffer)->Reset();
            (*ppBuffer)->SetRequestId(CamxInvalidRequestId);
        }

        CAMX_FREE(pNode);
    }
    else
    {
        CAMX_LOG_ERROR(CamxLogGroupUtils, "Out of memory");
        result = CamxResultENoMemory;
    }

    m_pLock->Unlock();

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::GetBufferForRequest
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult CmdBufferManager::GetBufferForRequest(
    UINT64           requestId,
    PacketResource** ppBuffer)
{
    CamxResult result = GetBuffer(ppBuffer);

    m_pLock->Lock();

    if (CamxResultSuccess == result)
    {
        CAMX_ASSERT(NULL != *ppBuffer);

        (*ppBuffer)->SetRequestId(requestId);

        LDLLNode* pNode = static_cast<LDLLNode*>(CAMX_CALLOC(sizeof(LDLLNode)));

        if (NULL != pNode)
        {
            pNode->pData = *ppBuffer;

            m_busyPool.InsertToTail(pNode);
        }
        else
        {
            result = CamxResultENoMemory;
        }
    }

    m_pLock->Unlock();

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// CmdBufferManager::CheckBufferWithRequest
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult CmdBufferManager::CheckBufferWithRequest(
    UINT64           requestId,
    PacketResource** ppBuffer)
{
    m_pLock->Lock();

    CamxResult result = CamxResultEFailed;
    LDLLNode*  pNode  = m_busyPool.Head();

    // This code assumes there is only one buffer that matches
    while (NULL != pNode)
    {
        PacketResource* pResource = static_cast<PacketResource*>(pNode->pData);
        CAMX_ASSERT(NULL != pResource);

        if ((NULL != pResource) && (requestId == pResource->GetRequestId()))
        {
            result    = CamxResultSuccess;
            *ppBuffer = pResource;
            break;
        }

        pNode = LightweightDoublyLinkedList::NextNode(pNode);
    }

    m_pLock->Unlock();

    return result;
}

CAMX_NAMESPACE_END
